#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/bio.h>
#include <linux/blkdev.h>
#include <scsi/scsi_device.h>

MODULE_AUTHOR("Arkis");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("test-page");


static const char *g_devpath = "/dev/sdc";
static struct block_device *g_blkdev = NULL;
static struct bdev_handle *g_bdev_handle = NULL;

enum io_type {
    IOTYPE_READ = 0,
    IOTYPE_WRITE,
};

static int get_blk_device(void)
{
    g_bdev_handle = bdev_open_by_path(g_devpath, BLK_OPEN_READ | BLK_OPEN_WRITE, NULL, NULL);
    if (g_bdev_handle == NULL) {
        printk(KERN_ERR "Lookup bdev failed.\n");
		return -1;
    }

    g_blkdev = g_bdev_handle->bdev;
	if (IS_ERR(g_blkdev)) {
        printk(KERN_ERR "Get device failed [%s]\n", g_devpath);
        g_blkdev = NULL;
        bdev_release(g_bdev_handle);
		return -1;
	}
    printk(KERN_INFO "Get device success\n");
    return 0;
}

int submit_io(struct page *page, enum io_type iotype)
{
    int ret = 0;
    struct bio *bio;
    blk_opf_t opf = 0;

    if (iotype == IOTYPE_READ) {
        opf = REQ_OP_READ;	
    } else if (iotype == IOTYPE_WRITE){
        opf = REQ_OP_WRITE;
    } else {
        printk(KERN_ERR "Unsupported io type {%d}\n", iotype);
        return -1;
    }
    
    bio = bio_alloc(g_blkdev, 1, opf, GFP_KERNEL);  // 创建bio结构
    if (bio == NULL) {
        printk(KERN_ERR "Failed to allocate bio.\n");
        return -1;
    }
    
    ret = bio_add_page(bio, page, PAGE_SIZE, 0);  // 将page添加到bio
    if (ret != PAGE_SIZE) {
        printk(KERN_ERR "Failed to add page to bio.\n");
        bio_put(bio);
        return -1;
    }
    
    bio->bi_iter.bi_sector = 0;  // 设置bio的起始扇区
    bio_set_dev(bio, g_blkdev);  // 设置目标设备
    submit_bio_wait(bio);  // 提交bio请求
    
    // 检查IO状态
    if (bio->bi_status != BLK_STS_OK) {
        printk(KERN_ERR "Io error, status{%d}\n", bio->bi_status);
        ret = -1;
    }
    
    bio_put(bio);
    return ret;
}

void startio(void)
{
    int ret = -1;
    char *data = NULL;
    struct page *data_page = NULL;

    ret = get_blk_device();
    if (ret) {
        printk(KERN_ERR "Failed to get block device\n");
        return;
    }

    data_page = alloc_page(GFP_KERNEL);
    if (data_page == NULL) {
        printk(KERN_ERR "alloc_page failed\n");
        return;
    }
    data = kmap(data_page);
    memset(data, 0, PAGE_SIZE);
    strncpy(data, "Hello, World!", strlen("Hello, World!"));
    kunmap(data_page);
    data = NULL;

    ret = submit_io(data_page, IOTYPE_WRITE);
    if (ret < 0) {
        printk(KERN_ERR "Failed to read/write data from/to device, ret{%d}\n", ret);
        __free_page(data_page);
        return;
    }

    data = kmap(data_page);
    memset(data, 0, PAGE_SIZE);
    kunmap(data_page);
    data = NULL;

    ret = submit_io(data_page, IOTYPE_READ);
    if (ret < 0) {
        printk(KERN_ERR "Failed to read/write data from/to device, ret{%d}\n", ret);
        __free_page(data_page);
        return;
    }
    printk(KERN_INFO "Successfully read/write data into/from page. {%d}\n", ret);
    
    data = kmap(data_page);
    printk(KERN_INFO "Data: %s\n", data);
    kunmap(data_page);
    __free_page(data_page);
}

static int __init test_init(void)
{
    printk("test_start\n");
    startio();
    printk("test_done\n");
    return 0;
}

static void __exit test_exit(void)
{
    printk("test_exit\n");
    if (g_bdev_handle != NULL) {
        bdev_release(g_bdev_handle);
    }
}

module_init(test_init)
module_exit(test_exit)
